﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using SimpleIR;
using PIPostAnalysis.UserQuery;

namespace CodeExplorer
{
	public partial class CodeExplorer : Form
	{
		public static string ROOT_STRING = BaseDirString();

		private static string BaseDirString()
		{
			String cp = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
			int cmdloc = cp.LastIndexOf(@"jackalope");
			if(cmdloc != -1)
			{
				cmdloc = cp.IndexOf(@"\", cmdloc);
				cmdloc = cp.IndexOf(@"\", cmdloc + 1);
			}

			if(cmdloc == -1)
			{
				Console.WriteLine("Cannot move outside of jackalope directory!!!");
				System.Environment.Exit(1);
			}

			string rp = cp.Substring(0, cmdloc + 1);
			return rp;
		}

		public static string DUMP_DIR = ROOT_STRING + @"Data\";
		public static string SERIALIZE_DIR = ROOT_STRING + @"Data\ResultsDump";

		public static string CodeDirectory = @"C:\Users\mark\Documents\Visual Studio 2010\Projects\jackalope\DevMark\Data\Tests\Olden\BH\BH\";
		public static string ExeFile = @"C:\Users\mark\Documents\Visual Studio 2010\Projects\jackalope\DevMark\Data\Tests\Olden\BH\BH\bin\Debug\BH.exe";

		public static Timer LoadTimer = null;
		public static System.Threading.Tasks.Task LoadTask = null;
		public static System.Diagnostics.Stopwatch AnalyzeWatch = null;

		public static IRProgram CurrentProgram = null;
		public static Dictionary<IRInvokeIdentity, ParameterInfo> CurrentEXEMap = null;
		public static IRInvokeIdentity CurrentInvoke = null;

		public CodeExplorer()
		{
			InitializeComponent();

			this.MethodSelector.Text = "Select a Method";
			this.TreeView.Nodes.Add("Load an Assembly");

			this.LoadMenuItem.ShortcutKeys = Keys.Control | Keys.L;
			this.LoadMenuItem.ShowShortcutKeys = true;
		}

		private void Load_Action(object sender, EventArgs e)
		{
			//////
			//We need to set the code directory and the exefile here
			OpenFileDialog ofd = new OpenFileDialog();
			ofd.InitialDirectory = System.IO.Path.Combine(ROOT_STRING + @"Data\Tests");
			ofd.Filter = ".Net assembly|*.exe";

			DialogResult fres = ofd.ShowDialog();
			if(fres == System.Windows.Forms.DialogResult.OK)
			{
				ExeFile = ofd.FileName;
				CodeDirectory = System.IO.Path.GetDirectoryName(ExeFile);
				CodeDirectory = System.IO.Directory.GetParent(CodeDirectory).Parent.FullName;

				this.AnalysisLabel.Visible = true;
				this.AnalysisProgress.Visible = true;

				this.AnalysisProgress.Minimum = 1;
				this.AnalysisProgress.Maximum = 60;
				this.AnalysisProgress.Step = 1;
				this.AnalysisProgress.Value = 1;

				LoadTimer = new Timer();
				LoadTimer.Tick += this.Load_ActionTimerDone;
				LoadTimer.Interval = 100;
				LoadTimer.Start();

				AnalyzeWatch = new System.Diagnostics.Stopwatch();

				LoadTask = new System.Threading.Tasks.Task(this.DoAssemblyLoad);
				LoadTask.Start();
			}
		}

		private void Load_ActionTimerDone(object sender, EventArgs e)
		{
			if(!LoadTask.IsCompleted)
			{
				this.AnalysisProgress.Value = Math.Max(1, this.AnalysisProgress.Value % this.AnalysisProgress.Maximum);
				this.AnalysisProgress.PerformStep();
			}
			else
			{
				LoadTimer.Stop();
				LoadTimer = null;

				this.DoInfoInitialize();

				this.AnalysisProgress.Visible = false;

				this.AnalysisLabel.Text = String.Format("Time: {0:F2} seconds", AnalyzeWatch.Elapsed.Seconds);
			}
		}

		private void ByParameter_CheckedChanged(object sender, EventArgs e)
		{
			this.UpdateTreeView();
		}

		private void SelectMethod(object sender, EventArgs e)
		{
			string msel = (string)this.MethodSelector.SelectedItem;
			IRInvokeIdentity iiv = CurrentEXEMap.Keys.FirstOrDefault((IRInvokeIdentity ii) => (ii.DefInClass.TypeName + "::" + ii.Sig.InvokeName).Equals(msel));
			CurrentInvoke = iiv;

			this.UpdateTreeView();
			this.UpdateCodeView();
		}

		private void TreeView_Select(object sender, EventArgs e)
		{
			this.UpdateCodeView();
		}

		private void UpdateCodeView()
		{
			if(CurrentInvoke == null)
				this.CodeBrowser.DocumentText = "";

			IRBodyDef tbdf = CurrentProgram.MethodBodyTable.GetBodyForInvokeExactSigMatch(CurrentInvoke);
			string infile = tbdf.Body.BasicBlockList[0].Operations[0].SourceLocationInfo.FileName;
			string infd = System.IO.Path.Combine(CodeDirectory, System.IO.Path.GetFileName(infile));
			string[] cv = System.IO.File.ReadLines(infd).ToArray();

			int minline = Int32.MaxValue;
			int maxline = Int32.MinValue;
			foreach(IRBasicBlock bb in tbdf.Body.BasicBlockList)
			{
				foreach(IRByteCode bc in bb.Operations)
				{
					if(bc.SourceLocationInfo.IsDummyLocation)
						continue;

					minline = Math.Min(minline, bc.SourceLocationInfo.Line);
					maxline = Math.Max(maxline, bc.SourceLocationInfo.Line);
				}
			}

			minline = minline - 4;
			maxline = maxline + 4;
			
			HashSet<int> usedlines = new HashSet<int>();
			HashSet<int> modlines = new HashSet<int>();
			this.GetHighlightLines(usedlines, modlines);

			StringBuilder sb = new StringBuilder();
			System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(sb);

			xw.WriteStartElement("html");
			xw.WriteStartElement("body");
			xw.WriteStartElement("code");

			xw.WriteString("...\n");
			for(int i = 0; i < cv.Length; ++i)
			{
				if(minline <= i && i <= maxline)
				{
					string clr = ColorizeLine(i + 1, usedlines, modlines);
					if(clr != null)
						xw.WriteRaw(clr);

					xw.WriteString(cv[i]);

					if(clr != null)
						xw.WriteRaw("</font>");

					xw.WriteString("\n");
				}
			}
			xw.WriteString("...\n");

			xw.WriteEndElement();
			xw.WriteEndElement();
			xw.WriteEndElement();
			xw.Close();

			sb.Replace("\n", "<br>");
			sb.Replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;");
			this.CodeBrowser.DocumentText = sb.ToString();
		}

		private static string ColorizeLine(int line, HashSet<int> usedlines, HashSet<int> modlines)
		{
			if(usedlines.Contains(line) && modlines.Contains(line))
				return "<font color=\"RED\">";
			else if(modlines.Contains(line))
				return "<font color=\"GOLD\">";
			else if(usedlines.Contains(line))
				return "<font color=\"BLUE\">";
			else
				return null;
		}

		private void GetHighlightLines(HashSet<int> usedlines, HashSet<int> modlines)
		{
			TreeNode stn = this.TreeView.SelectedNode;
			if(stn == null)
				return;

			IRRootIdentifier rr = null;
			IRType tt = null;
			IRMemberField mf = null;

			if(stn.Tag.Equals("Parameter"))
			{
				string pval = (string)stn.Text;
				rr = CurrentEXEMap[CurrentInvoke].PNameDataMap.FirstOrDefault((SingleParamInfo ssi) => ssi.ParameterName.RootNameString().Equals(pval)).ParameterName;
			}
			else if(stn.Tag.Equals("Type"))
			{
				string tval = (string)stn.Text;
				tt = CurrentProgram.TypeTable.AllDefinedTypes.FirstOrDefault((IRType ftt) => ftt.TypeName.Equals(tval));

				string pval = (string)stn.Parent.Text;
				rr = CurrentEXEMap[CurrentInvoke].PNameDataMap.FirstOrDefault((SingleParamInfo ssi) => ssi.ParameterName.RootNameString().Equals(pval)).ParameterName;
			}
			else if(stn.Tag.Equals("Field"))
			{
				string tval = (string)stn.Parent.Text;
				tt = CurrentProgram.TypeTable.AllDefinedTypes.FirstOrDefault((IRType ftt) => ftt.TypeName.Equals(tval));

				string fval = (string)stn.Text;
				mf = tt.TypeDescription.AllVisibleFields.FirstOrDefault((IRMemberField ff) => ff.FieldName.Equals(fval));

				string pval = (string)stn.Parent.Parent.Text;
				rr = CurrentEXEMap[CurrentInvoke].PNameDataMap.FirstOrDefault((SingleParamInfo ssi) => ssi.ParameterName.RootNameString().Equals(pval)).ParameterName;
			}
			else
			{
				throw new InvalidOperationException();
			}

			ParameterInfo cpi = CurrentEXEMap[CurrentInvoke];
			SingleParamInfo spi = cpi.PNameDataMap.Find((SingleParamInfo spp) => spp.ParameterName == rr);
			foreach(Tuple<IRType, PInfoModel.MemLocWPath, IRSourceLocation> ul in spi.UsedLocations)
			{
				if((tt == null || tt == ul.Item1) && (mf == null || (!ul.Item2.AccPath.IsEmptyAccessPath && mf == ul.Item2.AccPath.PathFirst)))
					usedlines.Add(ul.Item3.Line);
			}

			foreach(Tuple<IRType, PInfoModel.MemLocWPath, IRSourceLocation> ml in spi.ModLocations)
			{
				if((tt == null || tt == ml.Item1) && (mf == null || (!ml.Item2.AccPath.IsEmptyAccessPath && mf == ml.Item2.AccPath.PathFirst)))
					modlines.Add(ml.Item3.Line);
			}
		}

		private void DoInfoInitialize()
		{
			this.MethodSelector.Items.Clear();
			List<IRInvokeIdentity> iivl = new List<IRInvokeIdentity>(CurrentEXEMap.Keys);
			iivl.Sort((IRInvokeIdentity iv1, IRInvokeIdentity iv2) => iv1.ToString().CompareTo(iv2.ToString()));

			foreach(SimpleIR.IRInvokeIdentity iiv in iivl)
			{
				string rmn = iiv.DefInClass.TypeName + "::" + iiv.Sig.InvokeName;
				if(!iiv.DefInClass.TypeDescription.IsBuiltinType && !this.MethodSelector.Items.Contains(rmn))
					this.MethodSelector.Items.Add(rmn);
			}
			this.MethodSelector.Text = "Select a Method";

			this.UpdateTreeView();
		}

		private void UpdateTreeView()
		{
			this.TreeView.BeginUpdate();
			this.TreeView.Nodes.Clear();
			if(CurrentInvoke == null)
			{
				this.TreeView.Nodes.Add("Select a Method");
				this.TreeView.EndUpdate();
				return;
			}

			ParameterInfo cpi = CurrentEXEMap[CurrentInvoke];

			foreach(SingleParamInfo spi in cpi.PNameDataMap)
			{
				Dictionary<IRType, HashSet<string>> tvd = new Dictionary<IRType, HashSet<string>>();

				foreach(Tuple<IRType, PInfoModel.MemLocWPath, IRSourceLocation> ul in spi.UsedLocations)
				{
					if((!(ul.Item1 is IRClassType) && !(ul.Item1 is IRStructType)) || ul.Item2.AccPath.IsEmptyAccessPath)
						continue;

					if(!tvd.ContainsKey(ul.Item1))
						tvd.Add(ul.Item1, new HashSet<string>());
					tvd[ul.Item1].Add(ul.Item2.AccPath.PathFirst.FieldName);
				}

				foreach(Tuple<IRType, PInfoModel.MemLocWPath, IRSourceLocation> ml in spi.ModLocations)
				{
					if((!(ml.Item1 is IRClassType) && !(ml.Item1 is IRStructType)) || ml.Item2.AccPath.IsEmptyAccessPath)
						continue;

					if(!tvd.ContainsKey(ml.Item1))
						tvd.Add(ml.Item1, new HashSet<string>());
					tvd[ml.Item1].Add(ml.Item2.AccPath.PathFirst.FieldName);
				}

				List<IRType> alltl = new List<IRType>(tvd.Keys);
				alltl.Sort((IRType t1, IRType t2) => t1.TypeName.CompareTo(t2.TypeName));

				int apos = this.TreeView.Nodes.Count;
				this.TreeView.Nodes.Add(spi.ParameterName.RootNameString());
				this.TreeView.Nodes[apos].Tag = "Parameter";
				foreach(IRType tt in alltl)
				{
					int ppos = this.TreeView.Nodes[apos].Nodes.Count;
					this.TreeView.Nodes[apos].Nodes.Add(tt.TypeName);
					this.TreeView.Nodes[apos].Nodes[ppos].Tag = "Type";
					List<string> allf = new List<string>(tvd[tt]);
					allf.Sort();

					foreach(string ts in allf)
					{
						int ccpos = this.TreeView.Nodes[apos].Nodes[ppos].Nodes.Count;
						this.TreeView.Nodes[apos].Nodes[ppos].Nodes.Add(ts);
						this.TreeView.Nodes[apos].Nodes[ppos].Nodes[ccpos].Tag = "Field";
					}
				}
			}

			this.TreeView.EndUpdate();
		}

		private void DoAssemblyLoad()
		{
			SimpleIR.IRProgram p = CCITranslator.CCIExtractor.Extract(ExeFile, ROOT_STRING);
			p.LoadTypeInfo();
			p.PreProcLinkAndSimplify();

			p.BuildT1T2();

			CurrentProgram = p;
			
			RgnAnalyzer.GlobalAnalysisState.LoadProg(p, DUMP_DIR, SERIALIZE_DIR);

			//do the analysis
			AnalyzeWatch.Start();
			RgnAnalyzer.GlobalAnalysisState.AnalyzeMain(true);
			AnalyzeWatch.Stop();

			//time post processing
			PInfoModel.CompleteAnalysisInfoForProgram fpinfo = RgnAnalyzer.GlobalAnalysisState.ComputeFullAnalysisInfo(true);
			CurrentEXEMap = PIPostAnalysis.UserQuery.ParameterInfoCompute.ComputePInfo(fpinfo);
			PInfoModel.CompleteAnalysisInfoMethodContext.CleanUpProgPointInfoTables();
		}
	}
}
